const std = @import("std");
const r = @import("../resource.zig");

const log = std.log.scoped(.IO);

pub fn ioSystem(io_state: *r.IO) anyerror!void {
    var cqes: [r.IO.BACKLOG]std.os.linux.io_uring_cqe = undefined;

    const count = try io_state.ring.copy_cqes(&cqes, 0);

    for (cqes[0..count]) |cqe| {
        const i = cqe.user_data;
        var state = &io_state.states[i];

        try switch (state.*) {
            .PostAccept => |info| handlePostAccept(i, cqe, &io_state.ring, state, info),
            .PostRead => |info| handlePostRead(i, cqe, &io_state.ring, state, info),
            .PostWrite => |info| handlePostWrite(i, cqe, &io_state.ring, state, info),
            .PostClose => |info| handlePostClose(i, cqe, &io_state.ring, state, info),
            .Ready => error.InvalidState,
        };
    }

    var i: usize = 0;
    while (i < r.IO.BACKLOG) : (i += 1) {
        switch(io_state.states[i]) {
            .Ready => {
                io_state.states[i] = r.State{
                    .PostAccept = undefined
                };
                if (io_state.listener.sockfd) |fd| {
                    const address = &io_state.states[i].PostAccept.endpoint.any;
                    var sock_len: std.os.socklen_t = @sizeOf(std.net.Address);
                    _ = try io_state.ring.accept(i, fd, &io_state.states[i].PostAccept.endpoint.any, &sock_len, std.os.SOCK_CLOEXEC);
                } else {
                    unreachable;
                }
            },
            else => {},
        }
    }

    _ = try io_state.ring.submit();
}

fn handlePostAccept(
    state_id: u64, 
    cqe: std.os.linux.io_uring_cqe,
    io_uring: *std.os.linux.IO_Uring,
    state: *r.State,
    info: r.PostAcceptInfo,
) !void {
    if (cqe.res < 0) {
        return error.IOError;
    }

    const client_fd = cqe.res;
    state.* = r.State{
        .PostRead = r.PostReadInfo{
            .endpoint = info.endpoint,
            .buf = undefined, 
            .fd = client_fd,
        },
    };

    _ = try io_uring.read(state_id, client_fd, &state.PostRead.buf, 0);

    log.info("Accepted connection from {}", .{info.endpoint});
}

fn handlePostRead(
    state_id: u64, 
    cqe: std.os.linux.io_uring_cqe, 
    io_uring: *std.os.linux.IO_Uring,
    state: *r.State,
    info: r.PostReadInfo,
) !void {
    if (cqe.res < 0) {
        return error.IOError;
    }

    const read: usize = @bitCast(u32, cqe.res);
    state.* = r.State{
        .PostWrite = r.PostWriteInfo{
            .endpoint = info.endpoint,
            .buf = undefined,
            .fd = info.fd
        }
    };

    const slice = try std.fmt.bufPrint(&state.PostWrite.buf, "echo: {s}", .{info.buf[0..read]});

    _ = try io_uring.write(state_id, info.fd, slice, 0);

    log.info("Read {} bytes from {}", .{read, info.endpoint});
}

fn handlePostWrite(
    state_id: u64, 
    cqe: std.os.linux.io_uring_cqe, 
    io_uring: *std.os.linux.IO_Uring,
    state: *r.State,
    info: r.PostWriteInfo,
) !void {
    if (cqe.res < 0) {
        return error.IOError;
    }

    const written = cqe.res;
    state.* = r.State{
        .PostClose = r.PostCloseInfo{
            .endpoint = info.endpoint,
        }
    };

    _ = try io_uring.close(state_id, info.fd);

    log.info("Wrote {} bytes to {}", .{written, info.endpoint});
}

fn handlePostClose(
    state_id: u64, 
    cqe: std.os.linux.io_uring_cqe, 
    io_uring: *std.os.linux.IO_Uring,
    state: *r.State,
    info: r.PostCloseInfo,
) !void {
    if (cqe.res < 0) {
        return error.IOError;
    }
    
    state.* = r.State{
        .Ready = {}
    };

    log.info("Closed connection with {}", .{info.endpoint});
}